<?php
/**
 * Created by PhpStorm.
 * User: ari
 * Date: 12/25/2014
 * Time: 3:34 PM
 */
namespace BC\Games\Holdem;

use BC\Games\Holdem\Exceptions\HoldemPlayerException;
use BC\User\Session\UserSession;
use CPath\Request\Log\ILogListener;

class HoldemPlayer implements ILogListener
{
	const GROUP = 'player';
	const FLAG_CHECKED = 0x01;
	const FLAG_FOLDED = 0x02;
	const FLAG_HAS_BET = 0x04;
	const FLAG_READY = 0x08;

	private $mFlags = 0;
	private $mID;
	private $mSessionFingerprint;
	private $mBalance;
	private $mBetAmount = 0;
	private $mSidePot = array();
	private $mCards;

	/** @var ILogListener[] */
	private $mLogListeners = array();

	public function __construct($playerID, $sessionFingerprint, $balance) {
		$this->mID                  = $playerID;
		$this->mSessionFingerprint  = $sessionFingerprint;
		$this->mBalance             = (int)$balance;
		$this->mCards               = new Deck();
	}

	public function addBalance($addBalance) {
		$this->mBalance += $addBalance;
	}

	public function getBalance() {
		return $this->mBalance;
	}

	public function getRemainingBalance() {
		return $this->getBalance() - $this->getBetAmount();
	}

	public function isAllIn() {
		return $this->getBalance() === $this->getBetAmount();
	}

//	public function canBet($currentBetAmount) {
//		return ($this->getBalance() < $currentBetAmount);
//	}

	public function getPlayerID() {
		return $this->mID;
	}

//	public function getPublicKeyFingerprint() {
//		return $this->mSessionFingerprint;
//	}

	public function getDrawnCards() {
		return $this->mCards->getDrawnCards(self::GROUP);
	}

	public function deal($card) {
		$this->mCards->replay($card, self::GROUP);
		if (sizeof($this->mCards) > 2)
			throw new HoldemPlayerException("Player has more than 2 cards: " . $this->getPlayerID());
		$this->log("Dealer deals " . $this->getPlayerID() . ' a card');
	}

	public function callToAmount($callToAmount) {
		$callToAmount = (int)$callToAmount;
		if($callToAmount <= 0)
			throw new \InvalidArgumentException("Invalid amount");
		if ($callToAmount > $this->getBalance()) {
			throw new HoldemPlayerException("Bet is higher than balance: " . $callToAmount); }
		$this->mBetAmount = $callToAmount;
		$this->mFlags |= self::FLAG_HAS_BET;
		$this->log($this->getPlayerID() . ' calls to ' . $callToAmount);
	}

	public function raiseToAmount($raiseToAmount) {
		$raiseToAmount = (int)$raiseToAmount;
		if($raiseToAmount <= 0)
			throw new \InvalidArgumentException("Invalid amount");
		if ($raiseToAmount > $this->getBalance()) {
			throw new HoldemPlayerException("Bet is higher than balance: " . $raiseToAmount); }
		$this->mBetAmount = $raiseToAmount;
		$this->mFlags |= self::FLAG_HAS_BET;
		$this->log($this->getPlayerID() . ' raises to ' . $raiseToAmount);
	}

	public function allInToAmount($allInToAmount) {
		if ($allInToAmount > $this->getBalance()) {
			throw new HoldemPlayerException("Invalid All-in: " . $allInToAmount); }
		$this->mBetAmount = $allInToAmount;
		$this->mFlags |= self::FLAG_HAS_BET;
		$this->log($this->getPlayerID() . ' is all-in at ' . $allInToAmount);
	}

	public function betAmount($betAmount) {
		$betAmount = (int)$betAmount;
		if($betAmount <= 0)
			throw new \InvalidArgumentException("Invalid amount");
		if($betAmount === $this->getBetAmount())
			throw new \InvalidArgumentException("Bet is same as before");
		if ($betAmount > $this->getBalance()) {
			throw new HoldemPlayerException("Bet is higher than balance: " . $betAmount); }
		$old = $this->mBetAmount;
		$this->mBetAmount = $betAmount;
		$this->mFlags |= self::FLAG_HAS_BET;
		$bet = $betAmount - $old;
		$this->log($this->getPlayerID() . ' bets ' . $bet);
	}

	public function check() {
		if($this->hasFolded())
			throw new HoldemPlayerException("Player already folded: " . $this->getPlayerID());
		if($this->hasChecked()) {
			throw new HoldemPlayerException("Player already checked: " . $this->getPlayerID()); }
		$this->mFlags |= self::FLAG_CHECKED;
		$this->log($this->getPlayerID() . ' checks');
	}

	public function ready() {
		if($this->hasFolded())
			throw new HoldemPlayerException("Ready: Player already folded: " . $this->getPlayerID());
		if($this->hasChecked()) {
			throw new HoldemPlayerException("Ready: Player already checked: " . $this->getPlayerID()); }
		$this->mFlags |= self::FLAG_READY;
		$this->log($this->getPlayerID() . ' is ready');
	}

	public function fold() {
		if($this->hasFolded())
			throw new HoldemPlayerException("Player already folded: " . $this->getPlayerID());
		$this->mFlags |= self::FLAG_FOLDED;
		$this->log($this->getPlayerID() . ' folds');
	}

	public function isReady() {
		return $this->mFlags & self::FLAG_READY ? true: false;
	}

	public function hasFolded() {
		return $this->mFlags & self::FLAG_FOLDED ? true: false;
	}

	public function hasChecked() {
		return $this->mFlags & self::FLAG_CHECKED ? true: false;
	}

	public function hasBet() {
		return $this->mFlags & self::FLAG_HAS_BET ? true: false;
	}

	public function hasPair() {
		return $this->mCards->getDrawCount() === 2;
	}

	public function getBetAmount() {
		return $this->mBetAmount;
	}

	public function clearCheckAndBet() {
		$this->mFlags &= ~self::FLAG_CHECKED;
		$this->mFlags &= ~self::FLAG_HAS_BET;
	}

	public function reset() {
		if(!$this->hasFolded())
			$this->log($this->getPlayerID() . ' returns cards to dealer');
		$this->mBetAmount = 0;
		$this->mCards = new Deck();
		$this->mFlags &= ~self::FLAG_CHECKED;
		$this->mFlags &= ~self::FLAG_FOLDED;
		$this->mFlags &= ~self::FLAG_READY;
	}

	/**
	 * Add a log entry
	 * @param mixed $msg The log message
	 * @param int $flags [optional] log flags
	 * @return int the number of listeners that processed the log entry
	 */
	function log($msg, $flags = 0) {
		$c = 0;
		foreach ($this->mLogListeners as $Log)
			$c += $Log->log($msg, $flags);

		return $c;
	}

	/**
	 * Add a log listener callback
	 * @param ILogListener $Listener
	 * @return void
	 * @throws \InvalidArgumentException if this log listener inst does not accept additional listeners
	 */
	function addLogListener(ILogListener $Listener) {
		if (!in_array($Listener, $this->mLogListeners))
			$this->mLogListeners[] = $Listener;
	}

	/**
	 * @param $playerID
	 * @param $playerBalance
	 * @return int
	 */
	public function setSidePot($playerID, $playerBalance) {
		if($playerBalance >= $this->getBetAmount())
			throw new \InvalidArgumentException("Invalid side pot balance");
		if(!isset($this->mSidePot[$playerID])) {
			$this->mSidePot[$playerID] = $playerBalance;
			return $playerBalance;
		}
		return 0;
	}

	public function encrypt($Request, $contents, $additionalRecipients=null) {
		$UserSession = new UserSession($this->mSessionFingerprint);
		return $UserSession->encrypt($Request, $contents, $additionalRecipients);
	}
}